﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using YasharEl.Infrastructure.Diagnostics.Logging;
using YasharEl.Infrastructure.EntityModel;
using YasharEl.Infrastructure.ExceptionHandling;
using YasharEl.Infrastructure.ObjectFactory;
using YasharEl.Infrastructure.ProcessEngine.DomainModel;
using YasharEl.Infrastructure.ProcessEngine.PersistenceModel.Repositories;
using YasharEl.Infrastructure.ProcessEngine.PersistenceModel.Specifications;
using YasharEl.Infrastructure.ResourcesManagement;
using YasharEl.Infrastructure.WorkflowModel;

namespace YasharEl.Infrastructure.ProcessEngine.WorkflowModel
{
    public class ProcessEngineWorkflowCompletionHandler : IWorkflowCompletionHandler
    {
        #region ReadOnly Fields

        private readonly ILogger<ProcessEngineWorkflowCompletionHandler> logger;
        private readonly IDependencyResolver resolver;
        private readonly IExceptionManager exceptionManager;
        private readonly IProcessesRepository processesRepository;
        private readonly IUnitOfWork unitOfWork;
        private readonly IDateTimeProvider dateTimeProvider;

        #endregion

        #region Constructors

        public ProcessEngineWorkflowCompletionHandler(
            ILogger<ProcessEngineWorkflowCompletionHandler> logger,
            IDependencyResolver resolver,
            IExceptionManager exceptionManager,
            IProcessesRepository processesRepository,
            IUnitOfWork unitOfWork,
            IDateTimeProvider dateTimeProvider)
        {
            Contract.Requires<ArgumentNullException>(logger != null, "logger could not be null.");
            Contract.Requires<ArgumentNullException>(resolver != null, "resolver could not be null.");
            Contract.Requires<ArgumentNullException>(exceptionManager != null, "exceptionManager could not be null.");
            Contract.Requires<ArgumentNullException>(processesRepository != null, "processesRepository could not be null.");
            Contract.Requires<ArgumentNullException>(unitOfWork != null, "unitOfWork could not be null.");
            Contract.Requires<ArgumentNullException>(dateTimeProvider != null, "dateTimeProvider could not be null.");

            this.logger = logger;
            this.resolver = resolver;
            this.exceptionManager = exceptionManager;
            this.processesRepository = processesRepository;
            this.unitOfWork = unitOfWork;
            this.dateTimeProvider = dateTimeProvider;
        }

        #endregion

        #region IWorkflowCompletionHandler Members

        public void OnSuccessfulCompleted(Guid workflowInstanceId)
        {
            logger.Info("Workflow {0} successful completed.", workflowInstanceId);

            try
            {
                IProcess process = processesRepository
                     .Specify<IProcessSpecification>()
                     .WithWorkflowInstanceId(workflowInstanceId)
                     .ToResult()
                     .SingleOrDefault();

                if (process == null)
                    throw new ProcessNotFoundException(
                        resolver.Resolve<IResourceManager>(), 
                        workflowInstanceId
                    );

                process.ProcessStatus = ProcessStatus.Closed;
                process.ModifiedBy = process.CreatedBy;
                process.ModifiedDate = dateTimeProvider.Now;
                process.ClosedBy = process.CreatedBy;
                process.ClosedDate = dateTimeProvider.Now;

                unitOfWork.ExecuteTransactional(() =>
                {
                    processesRepository.Update(process);
                });

                // TODO: Publish IProcessSuccessfullyCompletedMessage message thro service bus here...

                logger.Info("OnSuccessfulCompleted finished processing of {0} workflow instance.", workflowInstanceId);
            }
            catch (Exception e)
            {
                bool rethrow = exceptionManager.HandleException(e, ProcessEngineConstants.DomainLayerExceptionPolicy);
                if (rethrow)
                {
                    throw;
                }
            }
        }

        public void OnFaulted(Guid workflowInstanceId, Exception fault)
        {
            logger.Warn("Workflow {0} faulted with error {1}", workflowInstanceId, fault);
        }

        public void OnCancelled(Guid workflowInstanceId)
        {
            logger.Info("Workflow {0} cancelled.", workflowInstanceId);
        }

        public void OnAborted(Guid workflowInstanceId, Exception fault)
        {
            logger.Warn("Workflow {0} aborted with error {1}", workflowInstanceId, fault);
        }

        #endregion
    }
}
